<?php
use think\Exception;
use think\Log;
use think\Config;
use think\Db;
use think\Loader;

class Generate
{
    private $module;
    private $name;
    private $dir;
    private $namespaceSuffix;
    private $nameLower;
    private $data;
    // 控制器黑名单
    private $blacklistName = [
        'Generate',
        'Index',
        'Admin',
        'Error'
    ];
    // 数据表黑名单
    private $blacklistTable = [
        'admin_user',
        'auth_group',
        'auth_group_access',
        'auth_rule',
        'admin_log'
    ];

    public function run($data, $option = 'all')
    {
        // 检查方法是否存在
        $action = 'build' . ucfirst($option);
        if (!method_exists($this, $action)) {
            throw new Exception('选项不存在：' . $option, 404);
        }
        // 载入默认配置
        $defaultConfigFile = APP_PATH . 'admin' . DS . 'extra' . DS . 'generate.php';
        if (file_exists($defaultConfigFile)) {
            $data = array_merge(include $defaultConfigFile, $data);
        }
        // 检查目录是否可写
        $pathCheck = APP_PATH . $data['module'] . DS;
        if (!self::checkWritable($pathCheck)) {
            throw new Exception("目录没有权限不可写，请执行一下命令修改权限：<br>chmod -R 755 " . realpath($pathCheck), 403);
        }
        // 将菜单全部转为小写
        if (isset($data['menu']) && $data['menu']) {
            foreach ($data['menu'] as &$menu) {
                $menu = strtolower($menu);
            }
        }
        $this->data = $data;
        $this->module = $data['module'];
        $controllers = explode(".", $data['controller']);
        $this->name = array_pop($controllers);
        $this->nameLower = Loader::parseName($this->name);

        // 分级控制器目录和命名空间后缀
        if ($controllers) {
            $this->dir = strtolower(implode(DS, $controllers) . DS);
            $this->namespaceSuffix = "\\" . strtolower(implode("\\", $controllers));
        } else {
            $this->dir = "";
            $this->namespaceSuffix = "";
        }

        // 数据表表名
        $tableName = str_replace(DS, '_', $this->dir) . $this->nameLower;

        // 判断是否在黑名单中
        if (in_array($data['controller'], $this->blacklistName)) {
            throw new Exception('该控制器不允许创建');
        }

        // 判断是否在数据表黑名单中
        if (isset($data['table']) && $data['table'] && in_array($tableName, $this->blacklistTable)) {
            throw new Exception('该数据表不允许创建');
        }

        // 创建目录
        $dir_list = ["view" . DS . $this->dir . $this->nameLower];
        if (isset($data['model']) && $data['model']) {
            $dir_list[] = "model" . DS . $this->dir;
        }
        if (isset($data['logic']) && $data['logic']) {
            $dir_list[] = "logic" . DS . $this->dir;
        }
        if (isset($data['validate']) && $data['validate']) {
            $dir_list[] = "validate" . DS . $this->dir;
        }
        if ($this->dir) {
            $dir_list[] = "controller" . DS . $this->dir;
        }
        $this->buildDir($dir_list);

        if ($action != 'buildDir') {
            // 文件路径
            $pathView = APP_PATH . $this->module . DS . "view" . DS . $this->dir . $this->nameLower . DS;
            $pathTemplate = APP_PATH . $this->module . DS . "view" . DS . "generate" . DS . "template" . DS;
            $fileName = APP_PATH . $this->module . DS . "%NAME%" . DS . $this->dir . $this->name . ".php";
            $code = $this->parseCode($data['table_index']);
            // 执行方法
            $this->$action($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        }
    }

    /**
     * 检查当前模块目录是否可写
     * @return bool
     */
    public static function checkWritable($path = null)
    {
        if (null === $path) {
            $path = APP_PATH . 'admin' . DS;
        }
        try {
            $testFile = $path . "bulid.test";
            if (!file_put_contents($testFile, "test")) {
                return false;
            }
            // 解除锁定
            unlink($testFile);

            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * 生成所有文件
     */
    private function buildAll($pathView, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        // 创建文件
        $this->buildIndex($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        $this->buildIndexAjax($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        $this->buildEdit($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        $this->buildAdd($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        $this->buildController($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        if (isset($data['validate']) && $data['validate']) {
            $this->buildValidate($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        }
        if (isset($data['model']) && $data['model']) {
            $this->buildModel($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        }
        if (isset($data['logic']) && $data['logic']) {
            $this->buildLogic($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        }
        if (isset($data['create_table']) && $data['create_table']) {
            $this->buildTable($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        }
        // 建立配置文件
        if (isset($data['create_config']) && $data['create_config']) {
            $this->buildConfig($pathView, $pathTemplate, $fileName, $tableName, $code, $data);
        }
    }

    /**
     * 创建目录
     */
    private function buildDir($dir_list)
    {
        foreach ($dir_list as $dir) {
            $path = APP_PATH . $this->module . DS . $dir;
            if (!is_dir($path)) {
                // 创建目录
                mkdir($path, 0755, true);
            }
        }
    }

    /**
     * 创建 add.html 文件
     */
    private function buildAdd($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        $template = file_get_contents($pathTemplate . "add.tpl");
        $file = $path . "add.html";

        return file_put_contents($file, str_replace(
            ["[TITLE]", "[ROWS]", "[SET_VALUE]", "[SCRIPT]"],
            [$this->data['title'], $code['add'], implode("\n", array_merge($code['set_checked'], $code['set_selected'])), implode("", $code['script_add'])],
            $template));
    }

    /**
     * 创建 edit.html 文件
     */
    private function buildEdit($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        $template = file_get_contents($pathTemplate . "edit.tpl");
        $file = $path . "edit.html";

        return file_put_contents($file, str_replace(
            ["[TITLE]", "[ROWS]", "[SET_VALUE]", "[SCRIPT]"],
            [$this->data['title'], $code['edit'], implode("\n", array_merge($code['set_checked'], $code['set_selected'])), implode("", $code['script_edit'])],
            $template));
    }

    /**
     * 创建 index.html 文件
     */
    private function buildIndex($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        $script = '';
        if ($code['script_search']) {
            $script = '{block name="script"}' . implode("", $code['script_search']) . "\n" . '{/block}' . "\n";
        }
        // 菜单全选的默认直接继承模板
        $menuArr = isset($this->data['menu']) ? $this->data['menu'] : [];
        $menu = '';
        if ($menuArr) {
            $menu = '{one:menu menu="' . implode(",", $menuArr) . '" /}';
        }
        $tdMenu = '';
        $tdMenu .= tab(4) . '{one:menu menu=\'edit\' url=\'edit:id=$vo.' . $data['table_index'] . '\' /}' . "\n";

        $form = implode("\n" . tab(1), $code['search']);
        $th = implode("\n" . tab(3), $code['th']);
        $td = implode("\n" . tab(3), $code['td']);
        $tdMenu .= tab(4) . '{one:menu menu=\'delete\' url=\'delete:id=$vo.' . $data['table_index'] . '\' /}';

        $template = file_get_contents($pathTemplate . "index.tpl");
        $file = $path . "index.html";

        return file_put_contents($file, str_replace(
                ["[TITLE]", "[FORM]", "[MENU]", "[TH]", "[TD]", "[TD_MENU]", "[SCRIPT]", "[PK]"],
                [$this->data['title'], $form, $menu, $th, $td, $tdMenu, $script, $data['table_index']],
                $template
            )
        );
    }

    /**
     * 创建 indexajax.html 文件
     */
    private function buildIndexAjax($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        $script = '';
        if ($code['script_search']) {
            $script = '{block name="script"}' . implode("", $code['script_search']) . "\n" . '{/block}' . "\n";
        }
        // 菜单全选的默认直接继承模板
        $menuArr = isset($this->data['menu']) ? $this->data['menu'] : [];
        $menu = '';
        if ($menuArr) {
            $menu = '{one:menu menu="' . implode(",", $menuArr) . '" /}';
        }
        $tdMenu = '';
        $tdMenu .= '{one:menu menu=\'edit\' url=\'edit:id=$vo.' . $data['table_index'] . '\' /}' . "\n";

        $form = implode("\n" . tab(1), $code['search']);
        $th = implode("\n" . tab(3), $code['th']);
        $td = implode("\n" . tab(3), $code['td']);
        $tdMenu .= tab(4) . '{one:menu menu=\'delete\' url=\'delete:id=$vo.' . $data['table_index'] . '\' /}';

        $template = file_get_contents($pathTemplate . "indexajax.tpl");
        $file = $path . "indexajax.html";

        return file_put_contents($file, str_replace(
                ["[FORM]", "[MENU]", "[TH]", "[TD]", "[TD_MENU]", "[SCRIPT]", "[PK]"],
                [$form, $menu, $th, $td, $tdMenu, $script, $data['table_index']],
                $template
            )
        );
    }

    /**
     * 创建控制器文件
     */
    private function buildController($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        $template = file_get_contents($pathTemplate . "Controller.tpl");
        $file = str_replace('%NAME%', 'controller', $fileName);

        return file_put_contents($file, str_replace(
                ["[TITLE]", "[NAME]", "[WHERE]", "[DATE]", "[PK]", "[NAMESPACE]"],
                [$this->data['title'], $this->name, $code['where'], $code['date'], $data['table_index'], $this->namespaceSuffix],
                $template
            )
        );
    }

    /**
     * 创建模型文件
     */
    private function buildModel($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        // 直接生成空模板
        $template = file_get_contents($pathTemplate . "Model.tpl");
        $file = str_replace('.php', 'Model.php', str_replace('%NAME%', 'model', $fileName));
        $autoTimestamp = '';
        if (isset($this->data['auto_timestamp']) && $this->data['auto_timestamp']) {
            $autoTimestamp = '// 开启自动写入时间戳字段' . "\n"
                . tab(1) . 'protected $autoWriteTimestamp = \'int\';';
        }

        return file_put_contents($file, str_replace(
                ["[TITLE]", "[NAME]", "[NAMESPACE]", "[TABLE]", "[AUTO_TIMESTAMP]"],
                [$this->data['title'], $this->name, $this->namespaceSuffix, $tableName, $autoTimestamp],
                $template
            )
        );
    }

    /**
     * 创建模型逻辑层文件
     */
    private function buildLogic($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        // 直接生成空模板
        $template = file_get_contents($pathTemplate . "Logic.tpl");
        $file = str_replace('%NAME%', 'model', $fileName);

        return file_put_contents($file, str_replace(
                ["[TITLE]", "[NAME]", "[NAMESPACE]", "[TABLE]"],
                [$this->data['title'], $this->name, $this->namespaceSuffix, $tableName],
                $template
            )
        );
    }

    /**
     * 创建验证器
     */
    private function buildValidate($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        $template = file_get_contents($pathTemplate . "Validate.tpl");
        $file = str_replace('%NAME%', 'validate', $fileName);

        return file_put_contents($file, str_replace(
                ["[TITLE]", "[NAME]", "[NAMESPACE]", "[RULE]", "[SCENE]"],
                [$this->data['title'], $this->name, $this->namespaceSuffix, $code['validate'], $code['scene']],
                $template
            )
        );
    }

    /**
     * 创建数据表
     */
    private function buildTable($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        // 一定别忘记表名前缀
        $tableName = isset($this->data['table_name']) && $this->data['table_name'] ?
            $this->data['table_name'] :
            Config::get("database.prefix") . $tableName;
        // 在 MySQL 中，DROP TABLE 语句自动提交事务，因此在此事务内的任何更改都不会被回滚，不能使用事务
        // http://php.net/manual/zh/pdo.rollback.php
        $tableExist = false;
        // 判断表是否存在
        $ret = Db::query("SHOW TABLES LIKE '{$tableName}'");
        // 表存在
        if ($ret && isset($ret[0])) {
            //不是强制建表但表存在时直接return
            if (!isset($this->data['create_table_force']) || !$this->data['create_table_force']) {
                return true;
            }
            Db::execute("RENAME TABLE {$tableName} to {$tableName}_build_bak");
            $tableExist = true;
        }
        $auto_create_field = ['create_time', 'update_time'];
        // 强制建表和不存在原表执行建表操作
        $fieldAttr = [];
        $key = [];
        if ($data['table_index'] && !in_array($data['table_index'], $auto_create_field)) {
            $fieldAttr[] = tab(1) . "`" . $data['table_index'] . "` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '{$this->data['title']}主键'";
        } else {
            ajax_return_error('未定义主键ID');
        }
        foreach ($this->data['field'] as $field) {
            if (!in_array($field['name'], $auto_create_field)) {
                // 字段属性
                $fieldAttr[] = tab(1) . "`{$field['name']}` {$field['type']}"
                    . ($field['extra'] ? ' ' . $field['extra'] : '')
                    . (isset($field['not_null']) && $field['not_null'] ? ' NOT NULL' : '')
                    . (strtolower($field['default']) == 'null' ? '' : " DEFAULT '{$field['default']}'")
                    . ($field['comment'] === '' ? '' : " COMMENT '{$field['comment']}'");
            }
            // 索引
            if (isset($field['key']) && $field['key'] && $field['name'] != $data['table_index']) {
                $key[] = tab(1) . "KEY `{$field['name']}` (`{$field['name']}`)";
            }
        }

        // 如果创建模型则自动生成create_time，update_time字段
        if (isset($this->data['auto_timestamp']) && $this->data['auto_timestamp']) {
            // 自动生成 create_time 字段，相应自动生成的模型也开启自动写入create_time和update_time时间，并且将类型指定为int类型
            $fieldAttr[] = tab(1) . "`create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间'";
            $fieldAttr[] = tab(1) . "`update_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间'";
        }
        // 默认自动创建主键为$data['table_index']
        $fieldAttr[] = tab(1) . "PRIMARY KEY (`" . $data['table_index'] . "`)";

        // 会删除之前的表，会清空数据，重新创建表，谨慎操作
        $sql_drop = "DROP TABLE IF EXISTS `{$tableName}`";
        // 默认字符编码为utf8，表引擎默认InnoDB，其他都是默认
        $sql_create = "CREATE TABLE `{$tableName}` (\n"
            . implode(",\n", array_merge($fieldAttr, $key))
            . "\n)ENGINE=" . (isset($this->data['table_engine']) ? $this->data['table_engine'] : 'InnoDB')
            . " DEFAULT CHARSET=utf8 COMMENT '{$this->data['title']}'";

        // 写入执行的SQL到日志中，如果不是想要的表结构，请到日志中搜索BUILD_SQL，找到执行的SQL到数据库GUI软件中修改执行，修改表结构
        Log::write("BUILD_SQL：\n{$sql_drop};\n{$sql_create};", Log::SQL);
        // execute和query方法都不支持传入分号 (;)，不支持一次执行多条 SQL
        try {
            Db::execute($sql_drop);
            Db::execute($sql_create);
            Db::execute("DROP TABLE IF EXISTS `{$tableName}_build_bak`");
        } catch (\Exception $e) {
            // 模拟事务操作，滚回原表
            if ($tableExist) {
                Db::execute("RENAME TABLE {$tableName}_build_bak to {$tableName}");
            }

            throw new Exception($e->getMessage());
        }
    }

    /**
     * 创建配置文件
     */
    private function buildConfig($path, $pathTemplate, $fileName, $tableName, $code, $data)
    {
        $content = '<?php' . "\n\n"
            . 'return ' . var_export($data, true) . ";\n";
        $file = $path . "config.php";

        return file_put_contents($file, $content);
    }


    /**
     * 创建文件的代码
     * @return array
     * return [
     * 'search'          => $search,
     * 'th'              => $th,
     * 'td'              => $td,
     * 'edit'            => $editField,
     * 'set_checked'     => $setChecked,
     * 'set_selected'    => $setSelected,
     * 'validate'        => $validate,
     * ];
     */
    private function parseCode($table_index)
    {
        // 生成 form.html 文件的代码
        $search = [''];
        // 生成 th.html 文件的代码
        $th = ['<th width=""><input type="checkbox"></th>'];
        // 生成 td.html 文件的代码
        $td = ['<td><input type="checkbox" name="id[]" value="{$vo.' . $table_index . '}"></td>'];
        // 生成 add.html 文件的代码
        $addField = '';
        // 生成 edit.html 文件的代码
        $editField = '';
        // radio类型的表单控件编辑状态使用javascript赋值
        $setChecked = [];
        // select类型的表单控件编辑状态使用javascript赋值
        $setSelected = [];
        // 搜索时被选中的值
        $searchSelected = '';
        //控制器条件筛选
        $where = '';
        //控制器date时间转换时间戳
        $date = '';
        // 生成验证器文件的代码
        $validate = '';
        $scene = '';
        $validateScene = '';
        // DatePicker脚本引入
        $scriptSearch = [];
        $scriptAdd = [];
        $scriptEdit = [];
        if (isset($this->data['form']) && $this->data['form']) {

            $index_form = $this->getIndexForm($this->data['form']);
            $addedit_form = $this->getAddeditForm($this->data['form']);

            foreach ($index_form as $form) {
                $options = $this->parseOption($form['option']);
                // 表单搜索
                if (isset($form['search']) && $form['search']) {
                    // 表单搜索
                    switch ($form['search_type']) {
                        case 'select':
                            // td
                            $td[] = '<td>{$vo.' . $form['name'] . '}</td>';
                            $where .= tab(2) . '$this->request->has(\'' . $form['name'] . '\', \'param\', true) && $where[\'' . $form['name'] . '\'] = $this->request->param(\'' . $form['name'] . '\');' . "\n";
                            // 默认选中
                            $search[] = tab(5) . '<select name="' . $form['name'] . '" class="form-control"><option value="">' . $form['title'] . '</option>';
                            $search = array_merge($search, $this->getOption('search', $form['name'], $options, $form, true, 3));
                            $search[] = tab(5) . '</select>';
                            break;
                        case 'date':
                            $date = '$data[\'' . $form['name'] . '\'] = time();';
                            // td
                            $td[] = '<td>{$vo.' . $form['name'] . '|date="Y-m-d H:i:s",###}</td>';
                            $where .= tab(2) . 'if ($this->request->has(\'start_' . $form['name'] . '\', \'param\', true) && $this->request->has(\'stop_' . $form['name'] . '\', \'param\', true)) {' . "\n"
                                . tab(3) . '$start_' . $form['name'] . ' = strtotime($this->request->param(\'start_' . $form['name'] . '\'));' . "\n"
                                . tab(3) . '$stop_' . $form['name'] . ' = strtotime($this->request->param(\'stop_' . $form['name'] . '\'));' . "\n"
                                . tab(3) . '$where[\'' . $form['name'] . '\'] = [\'between\', [$start_' . $form['name'] . ', $stop_' . $form['name'] . ']];' . "\n"
                                . tab(2) . '}' . "\n";
                            $search[] = tab(5) . '<input type="text" class="form-control layer-date laydate-icon" onclick="laydate()" '
                                . 'placeholder="开始' . $form['title'] . '" name="start_' . $form['name'] . '" '
                                . 'value="{$Request.param.start_' . $form['name'] . '}" '
                                . '> - '
                                . '<input type="text" class="form-control layer-date laydate-icon" onclick="laydate()" '
                                . 'placeholder="结束' . $form['title'] . '" name="stop_' . $form['name'] . '" '
                                . 'value="{$Request.param.stop_' . $form['name'] . '}" '
                                . '>';
                            $scriptSearch['date'] = '';
                            break;
                        default:
                            // td
                            $td[] = '<td>{$vo.' . $form['name'] . '}</td>';
                            $where .= '$this->request->has(\'' . $form['name'] . '\', \'param\', true) && $where[\'' . $form['name'] . '\'] = [\'like\', $this->request->param(\'' . $form['name'] . '\') . \'%\'];' . "\n";
                            $search[] = tab(5) . '<input type="text" class="form-control" '
                                . 'placeholder="' . $form['title'] . '" name="' . $form['name'] . '" '
                                . 'value="{$Request.param.' . $form['name'] . '}" '
                                . '>';
                            break;
                    }
                } else {
                    // td
                    $td[] = '<td>{$vo.' . $form['name'] . '}</td>';
                }
                // th
                if (isset($form['sort']) && $form['sort']) {
                    // 带有表单排序的需使用表单排序方法
                    $th[] = '<th width="">' . "{:sort_by('{$form['title']}','{$form['name']}')}</th>";
                } else {
                    $th[] = '<th width="">' . $form['title'] . "</th>";
                }
                // 像id这种白名单字段不需要自动生成到编辑页
                if (!in_array($form['name'], ['id', 'isdelete', 'create_time', 'update_time'])) {
                    if (isset($form['require']) && $form['require']) {
                        $defaultDesc = in_array($form['type'], ['checkbox', 'radio', 'select', 'date']) ? '选择' : '输入';
                        if($form['length']){
                            $validate .= tab(2) . '["' . $form['name'] . '", "require|max:' . $form['length'] . '", "请' . $defaultDesc . $form['title'] . '|' . $form['title'] . '最多不能超过' . $form['length'] . '个字符"],' . "\n";
                        }else{
                            $validate .= tab(2) . '["' . $form['name'] . '", "require", "请' . $defaultDesc . $form['title'] . '"],' . "\n";
                        }
                        $validateScene .= '\'' . $form['name'] . '\', ';
                    }
                }
            }

            foreach ($addedit_form as $form) {
                $options = $this->parseOption($form['option']);
                // 像id这种白名单字段不需要自动生成到编辑页
                if (!in_array($form['name'], ['id', 'isdelete', 'create_time', 'update_time'])) {
                    // 使用 Validform 插件前端验证数据格式，生成在表单控件上的验证规则
                    $validateForm = '';
                    $addField .= tab(6) . '<div class="form-group">' . "\n"
                        . tab(7) . '<label class="col-sm-3 control-label">'
                        . $form['title'] . '：</label>' . "\n"
                        . tab(7) . '<div class="col-sm-4'
                        . '">' . "\n";
                    $editField .= tab(6) . '<div class="form-group">' . "\n"
                        . tab(7) . '<label class="col-sm-3 control-label">'
                        . $form['title'] . '：</label>' . "\n"
                        . tab(7) . '<div class="col-sm-4'
                        . '">' . "\n";
                    switch ($form['type']) {
                        case "radio":
                            // radio类型的控件进行编辑状态赋值，checkbox类型控件请自行根据情况赋值
                            $name = $form['name'] . ($form['type'] == "checkbox" ? '[]' : '');

                            switch ($options[0]) {
                                case 'string':
                                    $addField .= $this->getRadio('add', $form, $name, $validateForm, $options[1], '', 0);
                                    $editField .= $this->getRadio('edit', $form, $name, $validateForm, $options[1], '', 0);
                                    break;
                                case 'var':
                                    $addField .= tab(4) . '{foreach name="$Think.config.conf.' . $options[1] . '" item=\'v\' key=\'k\'}' . "\n"
                                        . $this->getRadio('add', $form, $name, $validateForm, '{$v}', '{$k}', '{$k}')
                                        . tab(4) . '{/foreach}' . "\n";
                                    $editField .= tab(4) . '{foreach name="$Think.config.conf.' . $options[1] . '" item=\'v\' key=\'k\'}' . "\n"
                                        . $this->getRadio('edit', $form, $name, $validateForm, '{$v}', '{$k}', '{$k}')
                                        . tab(4) . '{/foreach}' . "\n";
                                    break;
                                case 'array':
                                    foreach ($options[1] as $option) {
                                        $addField .= $this->getRadio('add', $form, $name, $validateForm, $option[1], $option[0], $option[0]);
                                        $editField .= $this->getRadio('edit', $form, $name, $validateForm, $option[1], $option[0], $option[0]);
                                    }
                                    break;
                            }
                            break;
                        case "checkbox":
                            // 默认只生成一个空的示例控件，请根据情况自行复制编辑
                            $name = $form['name'] . ($form['type'] == "checkbox" ? '[]' : '');

                            switch ($options[0]) {
                                case 'string':
                                    $addField .= $this->getCheckbox('add', $form, $name, $validateForm, $options[1], '', 0);
                                    $editField .= $this->getCheckbox('edit', $form, $name, $validateForm, $options[1], '', 0);
                                    break;
                                case 'var':
                                    $addField .= tab(4) . '{foreach name="$Think.config.conf.' . $options[1] . '" item=\'v\' key=\'k\'}' . "\n"
                                        . $this->getCheckbox('add', $form, $name, $validateForm, '{$v}', '{$k}', '{$k}')
                                        . tab(4) . '{/foreach}' . "\n";
                                    $editField .= tab(4) . '{foreach name="$Think.config.conf.' . $options[1] . '" item=\'v\' key=\'k\'}' . "\n"
                                        . $this->getCheckbox('edit', $form, $name, $validateForm, '{$v}', '{$k}', '{$k}')
                                        . tab(4) . '{/foreach}' . "\n";
                                    break;
                                case 'array':
                                    foreach ($options[1] as $option) {
                                        $addField .= $this->getCheckbox('add', $form, $name, $validateForm, $option[1], $option[0], $option[0]);
                                        $editField .= $this->getCheckbox('edit', $form, $name, $validateForm, $option[1], $option[0], $option[0]);
                                    }
                                    break;
                            }
                            break;
                        case "select":
                            // select类型的控件进行编辑状态赋值
                            $addField .= tab(4) . "\n"
                                . tab(4) . '<select name="' . $form['name'] . '" class="form-control"' . $validateForm . '>' . "\n"
                                . implode("\n", $this->getOption('add', $form['name'], $options, $form, false, 6)) . "\n"
                                . tab(4) . '</select>' . "\n";
                            $editField .= tab(4) . "\n"
                                . tab(4) . '<select name="' . $form['name'] . '" class="form-control"' . $validateForm . '>' . "\n"
                                . implode("\n", $this->getOption('edit', $form['name'], $options, $form, false, 6)) . "\n"
                                . tab(4) . '</select>' . "\n";
                            break;
                        case "textarea":
                            // 默认生成的textarea加入了输入字符长度实时统计，H-ui.admin官方的textarealength方法有问题，请使用 tpadmin 框架修改后的源码，也可拷贝 H-ui.js 里相应的方法
                            // 如果不需要字符长度实时统计，请在生成代码中删除textarea上的onKeyUp事件和下面p标签那行
                            $addField .= tab(4) . '<textarea class="form-control" placeholder="" name="' . $form['name'] . '" '
                                . 'onKeyUp="textarealength(this, 255)"' . $validateForm . '>'
                                . (isset($form['default']) ? $form['default'] : ' ')
                                . '</textarea>' . "\n"
                                . tab(4) . '<p class="textarea-numberbar"><em class="textarea-length">0</em>/255</p>' . "\n";
                            $editField .= tab(4) . '<textarea class="form-control" placeholder="" name="' . $form['name'] . '" '
                                . 'onKeyUp="textarealength(this, 255)"' . $validateForm . '>'
                                . '{$li.' . $form['name'] . ' ?? \'\'}'
                                . '</textarea>' . "\n"
                                . tab(4) . '<p class="textarea-numberbar"><em class="textarea-length">0</em>/255</p>' . "\n";
                            break;
                        case "date":
                            $addField .= tab(4) . '<input type="text" class="form-control layer-date" '
                                . 'placeholder="' . $form['title'] . '" name="' . $form['name'] . '" '
                                . 'value="' . (isset($form['default']) ? $form['default'] . '" ' : '" ')
                                . $validateForm . '>' . "\n";
                            $scriptAdd['date'] = '';
                            $editField .= tab(4) . '<input type="text" class="form-control layer-date" '
                                . 'placeholder="' . $form['title'] . '" name="' . $form['name'] . '" '
                                . 'value="' . '{$li.' . $form['name'] . '|date="Y-m-d H:i:s",###}' . '" '
                                . $validateForm . '>' . "\n";
                            $scriptEdit['date'] = '';
                            break;
                        //case "text":
                        //case "password":
                            //图片上传
                        case "image":
                            //富文本编辑器
                        case "editor":
                        case "number":
                            $addField .= tab(8) . '<input type="' . $form['type'] . '" class="form-control" '
                                . 'placeholder="' . $form['title'] . '" name="' . $form['name'] . '" '
                                . 'value="' . (isset($form['default']) ? $form['default'] . '" ' : '" ')
                                . 'onkeyup="this.value=this.value.replace(/[^\d.]/g,\'\')" onpaste="this.value=this.value.replace(/[^\d.]/g,\'\')" '
                                . $validateForm . '>' . "\n";
                            $editField .= tab(8) . '<input type="' . $form['type'] . '" class="form-control" '
                                . 'placeholder="' . $form['title'] . '" name="' . $form['name'] . '" '
                                . 'value="' . '{$li.' . $form['name'] . ' ?? \'\'}' . '" '
                                . 'onkeyup="this.value=this.value.replace(/[^\d.]/g,\'\')" onpaste="this.value=this.value.replace(/[^\d.]/g,\'\')" '
                                . $validateForm . '>' . "\n";
                            break;
                        default:
                            $addField .= tab(8) . '<input type="' . $form['type'] . '" class="form-control" '
                                . 'placeholder="' . $form['title'] . '" name="' . $form['name'] . '" '
                                . 'value="' . (isset($form['default']) ? $form['default'] . '" ' : '" ')
                                . $validateForm . '>' . "\n";
                            $editField .= tab(8) . '<input type="' . $form['type'] . '" class="form-control" '
                                . 'placeholder="' . $form['title'] . '" name="' . $form['name'] . '" '
                                . 'value="' . '{$li.' . $form['name'] . ' ?? \'\'}' . '" '
                                . $validateForm . '>' . "\n";
                            break;
                    }
                    $addField .= tab(7) . '</div>' . "\n"
                        . tab(7) . '<div class="input-check col-sm-4"></div>' . "\n"
                        . tab(6) . '</div>' . "\n";
                    $editField .= tab(7) . '</div>' . "\n"
                        . tab(7) . '<div class="input-check col-sm-4"></div>' . "\n"
                        . tab(6) . '</div>' . "\n";
                }
            }
        }
        if ($search) {
            $search[] = tab(5) . '<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button>';
        }

        if ($validate) {
            $validate = substr($validate, 0, strlen($validate) - 2);
            $validate = 'protected $rule = [' . "\n" . $validate . "\n" . tab(1) . '];';
            $validateScene = substr($validateScene, 0, strlen($validateScene) - 2);
            $scene .= 'protected $scene = [' . "\n"
                . tab(2) . '\'add\' => [' . $validateScene . '],' . "\n"
                . tab(2) . '\'edit\' => [' . $validateScene . ']' . "\n" . tab(1) . '];';
        }

        return [
            'search' => $search,
            'th' => $th,
            'td' => $td,
            'add' => trim($addField),
            'edit' => trim($editField),
            'set_checked' => $setChecked,
            'set_selected' => $setSelected,
            'where' => $where,
            'date' => $date,
            'validate' => $validate,
            'scene' => $scene,
            'script_add' => $scriptAdd,
            'script_edit' => $scriptEdit,
            'script_search' => $scriptSearch,
        ];
    }

    /**
     * 获取列表页字段
     */
    private function getIndexForm($forms)
    {
        $new_forms = [];
        foreach ($forms as $val) {
            if (isset($val['show_index']) && $val['show_index'] == 1 && isset($val['show_index_sort']) && $val['show_index_sort'] != '') {
                $new_forms[] = $val;
            }
        }
        return list_sort_by($new_forms, 'show_index_sort');
    }

    /**
     * 获取添加修改页字段
     */
    private function getAddeditForm($forms)
    {
        $new_forms = [];
        foreach ($forms as $val) {
            if (isset($val['show_addedit']) && $val['show_addedit'] == 1 && isset($val['show_addedit_sort']) && $val['show_addedit_sort'] != '') {
                $new_forms[] = $val;
            }
        }
        return list_sort_by($new_forms, 'show_addedit_sort');
    }

    /**
     * 生成单选框
     */
    private function getRadio($type, $form, $name, $validateForm, $title, $value = '', $key = 0, $tab = 4)
    {
        return tab($tab + 3) . '<div class="radio pull-left">' . "\n"
        . tab($tab + 4) . '<input type="' . $form['type'] . '" name="' . $name . '" '
        . 'id="' . $form['name'] . '-' . $key . '" value="' . $value . '" ' . ($type == 'edit' ? '{if condition="$li.' . $name . ' == ' . $key . '"}checked="checked"{/if}' : '') . ($type == 'add' && isset($form['default']) && $value == $form['default'] ? ' checked="checked"' : '') . $validateForm . '>' . "\n"
        . tab($tab + 4) . '<label for="' . $form['name'] . '-' . $key . '">' . $title . '</label>' . "\n"
        . tab($tab + 3) . '</div>' . "\n";
    }

    /**
     * 生成复选框
     */
    private function getCheckbox($type, $form, $name, $validateForm, $title, $value = '', $key = 0, $tab = 4)
    {
        return tab($tab + 3) . '<div class="checkbox pull-left">' . "\n"
        . tab($tab + 4) . '<input type="' . $form['type'] . '" name="' . $name . '" '
        //. 'id="' . $form['name'] . '-' . $key . '" value="' . $value . '"' . $validateForm . '>' . "\n"
        . 'id="' . $form['name'] . '-' . $key . '" value="' . $value . '" ' . ($type == 'edit' ? '{if condition="in_array(\'' . $key . '\',$li.' . str_replace('[]', '', $name) . ')"}checked="checked"{/if}' : '') . ($type == 'add' && isset($form['default']) && $value == $form['default'] ? ' checked="checked"' : '') . $validateForm . '>' . "\n"
        . tab($tab + 4) . '<label for="' . $form['name'] . '-' . $key . '">' . $title . '</label>' . "\n"
        . tab($tab + 3) . '</div>' . "\n";
    }

    /**
     * 获取下拉框的option
     */
    private function getOption($type, $name, $options, $form, $empty = true, $tab = 3)
    {
        switch ($options[0]) {
            case 'string':
                return [tab($tab) . '<option value="">' . $options[1] . '</option>'];
                break;
            case 'var':
                $ret = [];
                if ($empty) {
                    $ret[] = tab($tab) . '<option value="">所有' . $form['title'] . '</option>';
                }
                $ret[] = tab($tab) . '{foreach name="$Think.config.conf.' . $options[1] . '" item=\'v\' key=\'k\'}';
                $ret[] = tab($tab + 1) . '<option value="{$k}">{$v}</option>';
                $ret[] = tab($tab) . '{/foreach}';

                return $ret;
                break;
            case 'array':
                $ret = [];
                foreach ($options[1] as $option) {
                    $ret[] = tab($tab + 3) . '<option value="' . $option[0] . '" '
                        . ($type == 'search' ? ($option[0] == 0 ? '{if condition="$Request.param.' . $name . ' != \'\' && $Request.param.' . $name . ' == ' . $option[0] . '"}selected{/if}' : '{if condition="$Request.param.' . $name . ' == ' . $option[0] . '"}selected{/if}') : '')
                        . ($type == 'edit' ? '{if condition="$li.' . $name . ' == ' . $option[0] . '"}selected{/if}' : ($type == 'add' && isset($form['default']) && $form['default'] == $option[0] ? 'selected' : ''))
                        . '>' . $option[1] . '</option>';
                }

                return $ret;
                break;
        }
    }

    /**
     * 格式化选项值
     */
    private function parseOption($option, $string = false)
    {
        if (!$option) return ['string', $option];
        // {vo.item} 这种格式传入的变量
        if (preg_match('/^\{(.*?)\}$/', $option, $match)) {
            return ['var', $match[1]];
        } else {
            if ($string) {
                return ['string', $option];
            }
            // key:val#key2:val2#val3#... 这种格式
            $ret = [];
            $arrVal = explode('#', $option);
            foreach ($arrVal as $val) {
                $keyVal = explode(':', $val, 2);
                if (count($keyVal) == 1) {
                    $ret[] = ['', $keyVal[0]];
                } else {
                    $ret[] = [$keyVal[0], $keyVal[1]];
                }
            }

            return ['array', $ret];
        }
    }
}
